package cn.codingxiaxw.web;

import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.codingxiaxw.dao.SuccessKilledDao;
import cn.codingxiaxw.dao.cache.RedisDao;
import cn.codingxiaxw.dto.Exposer;
import cn.codingxiaxw.dto.SeckillExecution;
import cn.codingxiaxw.dto.SeckillResult;
import cn.codingxiaxw.entity.Seckill;
import cn.codingxiaxw.enums.SeckillStatEnum;
import cn.codingxiaxw.exception.RepeatKillException;
import cn.codingxiaxw.exception.SeckillCloseException;
import cn.codingxiaxw.service.SeckillService;

/**
 * Created by codingBoy on 16/11/28.
 */
@Component
@RequestMapping("/seckill")//url:模块/资源/{}/细分
public class SeckillController implements InitializingBean
{
    private static final Logger log = LoggerFactory.getLogger(SeckillController.class);
    @Autowired
    private SeckillService seckillService;
    @Autowired
    private SuccessKilledDao successKilledDao;
    @Autowired
    private RedisDao redisDao;

    private ReentrantLock lock = new ReentrantLock();



    //加入一个混淆字符串(秒杀接口)的salt，为了我避免用户猜出我们的md5值，值任意给，越复杂越好
    private final String salt = "shsdssljdd'l.";

    private String getMD5(long seckillId) {
        String base = seckillId + "/" + salt;
        String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
        return md5;
    }

    public void deleteSuccessKilled(){
        successKilledDao.deleteAll();
        redisDao.clearAll();
    }

    /**
     * 测试 每次都重启服务,并且清空缓存
     * 20000 thread  执行3次  一共花费44412(剩余51),一共花费13053(剩余49),一共花费17323(剩余57)
     *
     * 经过调优后
     * 1.加锁
     * 20000 thread 一共花费89480,一共花费92264,一共花费90162(用锁后不再出现剩余的情况)
     * 2.加缓存
     * 在锁的逻辑里,查询redis,如果没了,直接返回,如果有执行插入,并更新redis里面的库存
     *20000 thread  一共花费24033,一共花费23398,一共花费22653  (很明显用了缓存速度提升大于4倍)
     *
     * @param num
     * @return
     * @throws InterruptedException
     */
    @ResponseBody
    @RequestMapping(value = "/test/{concurNum}",method = RequestMethod.GET)
    public String init(@PathVariable(value = "concurNum") int num) throws InterruptedException {
        deleteSuccessKilled();
        Long secKillId = 1000L;
        seckillService.updateNum(secKillId, 100);
        Seckill byId = seckillService.getById(secKillId);
        CountDownLatch countDownLatch = new CountDownLatch(num);
        String md5 = getMD5(secKillId);
        long start = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            Long phone = new Random().nextInt(1000000000) + 10000000000L;
            new Thread(() -> {
                SeckillResult<SeckillExecution> execute = execute(secKillId, md5, phone);
                System.out.println(Thread.currentThread().getName() + " - " +execute);
                countDownLatch.countDown();
            }, ""+i).start();
        }
        countDownLatch.await();
        System.out.println("一共花费"+(System.currentTimeMillis() - start));
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(seckillService.getById(secKillId));
        return "ok";
    }


    @RequestMapping(value = "/list",method = RequestMethod.GET)
    public String list(Model model)
    {
        //list.jsp+mode=ModelAndView
        //获取列表页
        List<Seckill> list=seckillService.getSeckillList();
        model.addAttribute("list",list);
        return "list";
    }

    @RequestMapping(value = "/{seckillId}/detail",method = RequestMethod.GET)
    public String detail(@PathVariable("seckillId") Long seckillId, Model model)
    {
        if (seckillId == null)
        {
            return "redirect:/seckill/list";
        }

        Seckill seckill=seckillService.getById(seckillId);
        if (seckill==null)
        {
            return "forward:/seckill/list";
        }

        model.addAttribute("seckill",seckill);

        return "detail";
    }

    //ajax ,json暴露秒杀接口的方法
    @RequestMapping(value = "/{seckillId}/exposer",
                    method = RequestMethod.GET,
                    produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public SeckillResult<Exposer> exposer(@PathVariable("seckillId") Long seckillId)
    {
        SeckillResult<Exposer> result;
        try{
            Exposer exposer=seckillService.exportSeckillUrl(seckillId);
            result=new SeckillResult<Exposer>(true,exposer);
        }catch (Exception e)
        {
            e.printStackTrace();
            result=new SeckillResult<Exposer>(false,e.getMessage());
        }

        return result;
    }

    /**
     * 把秒杀执行接口隐藏起来
     * @param seckillId
     * @param md5
     * @param userPhone
     * @return
     */
    @RequestMapping(value = "/{seckillId}/{md5}/execution",
            method = RequestMethod.POST,
            produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") Long seckillId,
                                                   @PathVariable("md5") String md5,
                                                   @CookieValue(value = "userPhone",required = false) Long userPhone)
    {
        if (userPhone==null)
        {
            return new SeckillResult<SeckillExecution>(false,"未注册");
        }
        Seckill seckill = null;
        SeckillResult<SeckillExecution> result;
        seckill = redisDao.getSeckill(seckillId);
        if (seckill.getNumber() <= 0 ) {
            SeckillExecution execution=new SeckillExecution(seckillId, SeckillStatEnum.END);
            return new SeckillResult<SeckillExecution>(true,execution);
        }
        try {
            lock.lock();
            seckill = redisDao.getSeckill(seckillId);
            if (seckill.getNumber() <= 0 ) {
                SeckillExecution execution=new SeckillExecution(seckillId, SeckillStatEnum.END);
                return new SeckillResult<SeckillExecution>(true,execution);
            }

//            SeckillExecution execution=new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS);
            SeckillExecution execution = seckillService.executeSeckill(seckillId, userPhone, md5);
            seckill.setNumber(seckill.getNumber() - 1);
            redisDao.putSeckill(seckill);
            return new SeckillResult<SeckillExecution>(true, execution);
        }catch (RepeatKillException e1)
        {
            SeckillExecution execution=new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);
            return new SeckillResult<SeckillExecution>(true,execution);
        }catch (SeckillCloseException e2)
        {
            SeckillExecution execution=new SeckillExecution(seckillId, SeckillStatEnum.END);
            return new SeckillResult<SeckillExecution>(true,execution);
        }
        catch (Exception e)
        {
            SeckillExecution execution=new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
            return new SeckillResult<SeckillExecution>(true,execution);
        }finally {
            lock.unlock();
        }

    }

    //获取系统时间
    @RequestMapping(value = "/time/now",method = RequestMethod.GET)
    @ResponseBody
    public SeckillResult<Long> time()
    {
        Date now=new Date();
        return new SeckillResult<Long>(true,now.getTime());
    }

    @Override
    public void afterPropertiesSet() throws Exception {

    }
}























